//
// Created by xiaoma on 1/31/21.
//

#include "BinaryFileParser.h"
#include <assert.h>

CodeObject *BinaryFileParser::parse() {
    int magic = inputStream->readInt();
    int modTime = inputStream->readInt();
    char objectType = inputStream->read();
    if (objectType == 'c') {
        CodeObject *result = getCodeObject();
        return result;
    }
    return nullptr;
}

CodeObject *BinaryFileParser::getCodeObject() {
    int argCount = inputStream->readInt();
    int localCount = inputStream->readInt();
    int stackSize = inputStream->readInt();
    int flag = inputStream->readInt();
    HiString *byteCode = getByteCode();
    ArrayList<HiObject *> *consts = getTuple();
    ArrayList<HiObject *> *names = getTuple();
    ArrayList<HiObject *> *varNames = getTuple();
    ArrayList<HiObject *> *freeVars = getTuple();
    ArrayList<HiObject *> *cellVars = getTuple();
    HiString *fileName = getName();
    HiString *moduleName = getName();
    int beginLineNo = inputStream->readInt();
    HiString *noTable = getNoTableName();

    return new CodeObject(argCount, localCount, stackSize, flag, byteCode,
                          consts, names, varNames, freeVars, cellVars, fileName, moduleName,
                          beginLineNo, noTable);
}

HiString * BinaryFileParser::getByteCode() {
    assert(inputStream->read() == 's');
    return getString();
}

HiString *BinaryFileParser::getString() {
    int len = inputStream->readInt();
    char *str = new char[len];
    for (int i = 0; i < len; ++i) {
        str[i] = inputStream->read();
    }
    auto result = new HiString(str, len);
    delete[] str;
    return result;
}

ArrayList<HiObject *> *BinaryFileParser::getTuple() {
    if (inputStream->read() != '(') {
        inputStream->unread();
        return nullptr;
    }
    int len = inputStream->readInt();
    HiString *str = nullptr;
    auto list = new ArrayList<HiObject *>(len);
    for (int i = 0; i < len; ++i) {
        char type = inputStream->read();
        switch (type) {
            case 'c':
                list->add(getCodeObject());
                break;
            case 'i':
                list->add(new HiInteger(inputStream->readInt()));
                break;
            case 'N':
                list->add(nullptr);
                break;
            case 't':
                str = getString();
                list->add(str);
                stringTable.add(str);
                break;
            case 's':
                list->add(getString());
                break;
            case 'R':
                list->add(stringTable.get(inputStream->readInt()));
                break;
            default:
                break;
        }
    }
    return list;
}

HiString *BinaryFileParser::getName() {
    char ch = inputStream->read();
    if (ch == 's') {
        return getString();
    } else if (ch == 't') {
        auto str = getString();
        stringTable.add(str);
        return str;
    } else if (ch == 'R') {
        return stringTable.get(inputStream->readInt());
    }
    return nullptr;
}

HiString *BinaryFileParser::getNoTableName() {
    char ch = inputStream->read();
    if (ch != 's' && ch != 't') {
        inputStream->unread();
        return nullptr;
    }
    return getName();
}

